package org.base.common.service.impl;

import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.base.common.annotation.SearchFiled;
import org.base.common.constant.SqlCondition;
import org.base.common.pojo.PageResult;
import org.base.common.query.Query;
import org.base.common.service.IService;
import org.base.common.util.Pair;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.List;

/**
 * 基础业务实现类
 **/
@Slf4j
public class ServiceImpl<M extends BaseMapper<T>, T, Q extends Query> extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<M, T> implements IService<T, Q> {

    @Override
    public List<T> getByQuery(Q query) {
        return lambdaQuery(query).list();
    }

    @Override
    public PageResult getByPageQuery(Q query) {
        final Integer page = query.getPage();
        final Integer pageSize = query.getPageSize();
        final Page<T> tPage = new Page<>(page, pageSize);
        final Page<T> pageResult = lambdaQuery(query).page(tPage);
        return new PageResult(pageResult.getCurrent(), pageResult.getSize(), pageResult.getRecords(), pageResult.getTotal());
    }

    @Override
    public LambdaQueryChainWrapper<T> lambdaQuery(Q query) {
        LambdaQueryChainWrapper<T> lambdaQuery = lambdaQuery();
        LocalDate beginDate = query.getBeginCreateDate();
        LocalDate endDate = query.getEndCreateDate();
        if (beginDate != null) {
            lambdaQuery.apply("create_time > {0}", beginDate);
        }
        if (endDate != null) {
            endDate = endDate.plusDays(1);
            lambdaQuery.apply("create_time < {0}", endDate);
        }
        String orderByColumn = query.getOrderByColumn();
        boolean orderByDesc = query.isOrderByDesc();
        SFunction orderByColumnFunc = query.getOrderByColumnFunc();

        // 默认排序
        if (StringUtils.isBlank(orderByColumn) && orderByColumnFunc == null) {
            lambdaQuery.getWrapper().getExpression().add(SqlKeyword.ORDER_BY, () -> "id", SqlKeyword.DESC);
        } else if (StringUtils.isNotBlank(orderByColumn)) {

            String[] camelCase = StringUtils.splitByCharacterTypeCamelCase(orderByColumn);
            for (int i = 0; i < camelCase.length; i++) {
                camelCase[i] = camelCase[i].toLowerCase();
            }
            orderByColumn = StringUtils.join(camelCase, "_");
            String _orderByColumn = orderByColumn;
            if (orderByDesc) {
                lambdaQuery.getWrapper().getExpression().add(SqlKeyword.ORDER_BY, () -> _orderByColumn, SqlKeyword.DESC);
            } else {
                lambdaQuery.getWrapper().getExpression().add(SqlKeyword.ORDER_BY, () -> _orderByColumn, SqlKeyword.ASC);
            }
        } else {
            if (orderByDesc) {
                lambdaQuery.orderByDesc(orderByColumnFunc);
            } else {
                lambdaQuery.orderByAsc(orderByColumnFunc);
            }
        }

        Long id = query.getId();
        if (id != null) {
            lambdaQuery.getWrapper().getExpression().add(() -> "id", SqlKeyword.EQ, () -> String.valueOf(id));
        }
        parseSearchCondition(lambdaQuery, query);
        return lambdaQuery;
    }

    /**
     * 解析查询条件
     * <p>
     * 下面方法有SQL注入风险
     *
     * @param lambdaQuery
     * @param query
     */
    private void parseSearchCondition(LambdaQueryChainWrapper<T> lambdaQuery, Q query) {
        if (query != null) {
            final Class<? extends Query> clazz = query.getClass();
            ReflectionUtils.doWithFields(clazz,
                    new ReflectionUtils.FieldCallback() {
                        @Override
                        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                            field.setAccessible(true);
                            final Object value = field.get(query);
                            if (value != null) {
                                final SearchFiled searchFiled = field.getAnnotation(SearchFiled.class);
                                String columnName = null;
                                if (searchFiled != null) {
                                    if (StringUtils.isNotBlank(searchFiled.columnName())) {
                                        columnName = searchFiled.columnName();
                                    } else if (StringUtils.isNotBlank(searchFiled.filedName())) {
                                        String[] camelCase = StringUtils.splitByCharacterTypeCamelCase(searchFiled.filedName());
                                        for (int i = 0; i < camelCase.length; i++) {
                                            camelCase[i] = camelCase[i].toLowerCase();
                                        }
                                        columnName = StringUtils.join(camelCase, "_");
                                    }
                                } else {
                                    final String fieldName = field.getName();
                                    String[] camelCase = StringUtils.splitByCharacterTypeCamelCase(fieldName);
                                    for (int i = 0; i < camelCase.length; i++) {
                                        camelCase[i] = camelCase[i].toLowerCase();
                                    }
                                    columnName = StringUtils.join(camelCase, "_");
                                }
                                String sqlColumn = columnName;
                                SqlCondition condition = SqlCondition.AUTO;
                                if (searchFiled != null) {
                                    condition = searchFiled.condition();
                                }
                                if (condition == SqlCondition.AUTO) {
                                    if (value instanceof String) {
                                        String sVal = String.valueOf(value);
                                        if (StringUtils.isNotBlank(sVal)) {
                                            sVal = StringEscapeUtils.escapeSql(sVal);
                                            lambdaQuery.getWrapper().apply(sqlColumn + SqlCondition.LIKE.getKeyword() + "'%" + sVal + "%'");
                                        }
                                    } else if (value instanceof Number) {
                                        lambdaQuery.getWrapper().apply(sqlColumn + SqlCondition.EQ.getKeyword() + "{0}", value);
                                    } else {
                                        log.warn(" {} 无法自动推断搜索方式 ", value);
                                    }
                                } else {
                                    if (condition == SqlCondition.IS_NOT_NULL || condition == SqlCondition.IS_NULL) {
                                        lambdaQuery.getWrapper().apply(sqlColumn + condition.getKeyword());
                                    } else {
                                        lambdaQuery.getWrapper().apply(sqlColumn + condition.getKeyword() + "{0}", value);
                                    }
                                }
                            }
                        }
                    }, f -> f.getDeclaringClass() == clazz);
        }
    }

    @Override
    public boolean isExist(Long id, SFunction<T, ?> filed, Object val) {
        return isExist(id, Pair.of(filed, val));
    }

    @Override
    public boolean isExist(Long id, Pair<SFunction<T, ?>, Object>... pairs) {
        Assert.notNull(pairs);
        Assert.noNullElements(pairs);
        final LambdaQueryChainWrapper<T> lambdaQuery = lambdaQuery();
        for (Pair<SFunction<T, ?>, Object> pair : pairs) {
            lambdaQuery.eq(pair.getLeft(), pair.getRight());
        }
        if (id != null) {
            lambdaQuery.getWrapper().getExpression().add(() -> "id", SqlKeyword.NE, () -> String.valueOf(id));
        }
        return lambdaQuery.count() > 0;
    }


}
